# PyQt5 Video player
#!/usr/bin/env python

from PyQt5.QtCore import QDir, Qt, QUrl, QStringListModel, QSize, QTimer
from PyQt5.QtWidgets import (QApplication, QFileDialog, QHBoxLayout, QLabel,
        QPushButton, QSizePolicy, QSlider, QStyle, QVBoxLayout, QWidget, QListWidget)
from PyQt5.QtWidgets import QMainWindow,QWidget, QPushButton, QAction, QListWidgetItem, QMessageBox, QFrame
from PyQt5.QtGui import QIcon, QPalette, QColor
import sys
import vlc

import json
import os

with open("config.json") as f:
    cfg = json.load(f)
    classes = cfg["classes"]

class Annotation:
    def __init__(self, vid_path):
        self.filename = vid_path[:-4]+'.json'
        if os.path.exists(self.filename):
            with open(self.filename) as f:
                self.data = json.load(f)
        else:
            self.data = {'actions': []}
    
    def add_action(self, pos, action):
        self.data['actions'].append([pos, action])
        self.save()
    
    def del_action(self, pos, action):
        self.data['actions'].remove([pos, action])
        self.save()
    
    def load_actions(self):
        return self.data['actions']
    
    def save(self):
        with open(self.filename, "w") as f:
            json.dump(self.data, f)

class VideoWindow(QMainWindow):

    def __init__(self, parent=None):
        super(VideoWindow, self).__init__(parent)
        self.setWindowTitle("PyQt Video Annotator") 

        self.instance = vlc.Instance()
        # creating an empty vlc media player
        self.player = self.instance.media_player_new()

        self.videoframe = QFrame()
        self.palette = self.videoframe.palette()
        self.palette.setColor (QPalette.Window,
                               QColor(0,0,0))
        self.videoframe.setPalette(self.palette)
        self.videoframe.setAutoFillBackground(True)

        self.playButton = QPushButton()
        self.playButton.setEnabled(False)
        self.playButton.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
        self.playButton.clicked.connect(self.play)

        self.positionSlider = QSlider(Qt.Horizontal)
        self.positionSlider.setRange(0, 1000)
        self.positionSlider.sliderMoved.connect(self.setPosition)

        self.errorLabel = QLabel()
        self.errorLabel.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum)

        # Create new action
        openAction = QAction(QIcon('open.png'), '&Open', self)        
        openAction.setShortcut('Ctrl+O')
        openAction.setStatusTip('Open video')
        openAction.triggered.connect(self.openFile)

        # Create exit action
        exitAction = QAction(QIcon('exit.png'), '&Exit', self)        
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('Exit application')
        exitAction.triggered.connect(self.exitCall)

        # Create menu bar and add action
        menuBar = self.menuBar()
        fileMenu = menuBar.addMenu('&File')
        #fileMenu.addAction(newAction)
        fileMenu.addAction(openAction)
        fileMenu.addAction(exitAction)

        # Create a widget for window contents
        wid = QWidget(self)
        self.setCentralWidget(wid)

        # Create layouts to place inside widget
        controlLayout = QHBoxLayout()
        controlLayout.setContentsMargins(0, 0, 0, 0)
        controlLayout.addWidget(self.playButton)
        controlLayout.addWidget(self.positionSlider)

        centerLayout = QVBoxLayout()
        centerLayout.addWidget(self.videoframe)
        centerLayout.addLayout(controlLayout)
        centerLayout.addWidget(self.errorLabel)

        
        self.listView = QListWidget()
        self.listView.setMaximumWidth(250)
        self.listView.itemDoubleClicked.connect(self.delete_action)
        self.listView.itemSelectionChanged.connect(self.action_selected)

        rightLayout = QVBoxLayout()
        rightLayout.addWidget(self.listView)

        layout = QHBoxLayout()
        layout.addLayout(centerLayout)
        layout.addLayout(rightLayout)

        # Set widget to contain window contents
        wid.setLayout(layout)

        # self.player.error.connect(self.handleError)

        self.grabKeyboard()

        self.timer = QTimer(self)
        self.timer.setInterval(200)
        self.timer.timeout.connect(self.updateUI)

        self.mspf = 40
        self.openFile()

    def updateUI(self):
        """updates the user interface"""
        # setting the slider to the desired position
        self.errorLabel.setText("frame: {}".format(self.get_frame_id()))
        self.positionSlider.setValue(self.player.get_position() * 1000)
    
    def get_frame_id(self):
        return int(self.player.get_time()*self.player.get_fps()/1000)
    
    def set_frame_id(self, frame):
        self.player.set_time(int(frame / self.player.get_fps() * 1000))
    
    def frame_forward(self):
        """Go forward one frame"""
        self.player.next_frame()

    def frame_backward(self):
        """Go backward one frame"""
        self.player.set_time(max(0, self.player.get_time() - self.mspf*2))

    def openFile(self):
        fileName, _ = QFileDialog.getOpenFileName(self, "Open Video", QDir.currentPath(), filter="*.mp4")

        if fileName != '':
            self.media = self.instance.media_new(fileName)
            # put the media in the media player
            self.player.set_media(self.media)

            # parse the metadata of the file
            self.media.parse()
            # set the title of the track as window title
            self.setWindowTitle(self.media.get_meta(0))

            # the media player has to be 'connected' to the QFrame
            # (otherwise a video would be displayed in it's own window)
            # this is platform specific!
            # you have to give the id of the QFrame (or similar object) to
            # vlc, different platforms have different functions for this
            if sys.platform.startswith('linux'): # for Linux using the X Server
                self.player.set_xwindow(self.videoframe.winId())
            elif sys.platform == "win32": # for Windows
                self.player.set_hwnd(self.videoframe.winId())
            elif sys.platform == "darwin": # for MacOS
                self.player.set_nsobject(int(self.videoframe.winId()))
            self.ann = Annotation(fileName)
            for pos, act in self.ann.load_actions():
                self.listView.insertItem(0, QListWidgetItem("{} {}".format(pos, act)))
            self.playButton.setEnabled(True)
            self.mspf = int(1000 // (self.player.get_fps() or 25))

    def exitCall(self):
        sys.exit(app.exec_())

    def play(self):
        """
            Toggle play/pause status
        """
        if self.player.is_playing():
            self.player.pause()
            self.playButton.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
        else:
            if self.player.play() == -1:
                self.OpenFile()
                return
            self.player.play()
            self.timer.start()
            self.playButton.setIcon(self.style().standardIcon(QStyle.SP_MediaPause))

    def setPosition(self, position):
        self.player.set_position(position)

    def handleError(self):
        self.playButton.setEnabled(False)
        self.errorLabel.setText("Error: " + self.player.errorString())
    
    def action_selected(self):
        items = self.listView.selectedItems()
        if items:
            pos, act = items[0].text().split()
            self.set_frame_id(int(pos))
    
    def delete_action(self, e, need_cfm=True):
        if need_cfm:
            cfm = QMessageBox.question(self, 'Video Annotator - Warrning', f"Do you comfirm to delete {e.text()}?", QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
        if not need_cfm or cfm == QMessageBox.Yes:
            pos, act = e.text().split()
            self.ann.del_action(int(pos), act)
            self.listView.takeItem(self.listView.row(e))
    
    def delete_lastaction(self):
        pos, act = self.listView.takeItem(0).text().split()
        self.ann.del_action(int(pos), act)
        if self.player.is_playing(): self.play()
        self.set_frame_id(int(pos))
        
    
    def keyPressEvent(self, QKeyEvent):
        if QKeyEvent.key() == Qt.Key_Space:
            self.play()
        if QKeyEvent.key() in [Qt.Key_Left, Qt.Key_A]:
            self.frame_backward()
        if QKeyEvent.key() in [Qt.Key_Right, Qt.Key_D]:
            self.frame_forward()
        if QKeyEvent.key() in [Qt.Key_Delete, Qt.Key_X]:
           self.delete_lastaction()
        
        if Qt.Key_0 <= QKeyEvent.key() < Qt.Key_8:
            pos = self.get_frame_id()
            act = classes[QKeyEvent.key()-Qt.Key_0]
            self.listView.insertItem(0, QListWidgetItem("{} {}".format(pos, act)))
            self.ann.add_action(pos, act)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    player = VideoWindow()
    player.resize(720, 480)
    player.show()
    sys.exit(app.exec_())
